import math
import traceback

import numpy as np
from xbase_util.common_util import entropy, get_res_status_code_list
from xbase_util.xbase_constant import http_req_path_pattern, res_status_code_pattern, http_req_method_pattern

from src.bean.cache_value_enum import CacheValueEnum
from src.bean.config_column_enum import ConfigColumnEnum
from src.constant import ColOtherFieldEnum, ValuePayloadVisibleEnum, ColTimeFieldEnum, ColEntropyFieldEnum, \
    ColStatusCodeCountFieldEnum, \
    ColGeoFieldEnum, \
    global_geo_util
from src.util.cache_value_util import CacheValueUtil
from src.util.common_util import get_internal_address, text_to_frequency_vector, printx
from src.util.config_manager import ConfigManager
from src.value_bean.value_geo_field_enum import ValueGeoFieldEnum
from src.value_bean.value_other_field_enum import ValueOtherFieldEnum


def get_geo(ip):
    if ip is None:
        return ValueGeoFieldEnum.ip_no_value.value, ValueGeoFieldEnum.ip_no_value.value, ValueGeoFieldEnum.ip_no_value.value
    ip = f"{ip}".replace("[", "").replace("]", "").replace("'", "").replace('"', "")
    is_internal = get_internal_address(ip)
    if is_internal:
        return is_internal, is_internal, is_internal
    try:
        r = global_geo_util.reader.city(ip)
        return (
            r.country.iso_code or ValueGeoFieldEnum.db_no_value.value,
            r.subdivisions.most_specific.name or ValueGeoFieldEnum.db_no_value.value,
            r.city.name or ValueGeoFieldEnum.db_no_value.value
        )
    except:
        return ValueGeoFieldEnum.db_no_value.value, ValueGeoFieldEnum.db_no_value.value, ValueGeoFieldEnum.db_no_value.value


def fill_geo(session):
    """
    填充GEO信息，对单独session使用
    :param session:
    :param is_sip: 是否源IP
    """
    xff_ip_iso_code, xff_ip_province, xff_ip_city = get_geo(session.get("http.xffIp"))
    sip_iso_code, sip_province, sip_city = get_geo(session.get("source.ip"))
    dip_iso_code, dip_province, dip_city = get_geo(session.get("destination.ip"))

    session.update({
        ColGeoFieldEnum.xffip_geo_country_iso.value: xff_ip_iso_code,
        ColGeoFieldEnum.xffip_geo_province_name.value: xff_ip_province,
        ColGeoFieldEnum.xffip_geo_city_name.value: xff_ip_city,

        ColGeoFieldEnum.sip_geo_country_iso.value: sip_iso_code,
        ColGeoFieldEnum.sip_geo_province_name.value: sip_province,
        ColGeoFieldEnum.sip_geo_city_name.value: sip_city,

        ColGeoFieldEnum.dip_geo_country_iso.value: dip_iso_code,
        ColGeoFieldEnum.dip_geo_province_name.value: dip_province,
        ColGeoFieldEnum.dip_geo_city_name.value: dip_city,

    })


def fill_session_before_split_packet(session, all_packets):
    """
    分割包之前的字段填充
    :param session:
    :param all_packets:
    """
    num_1, num_2, num_3, num_4, num_5 = get_res_status_code_list(all_packets)
    session.update({
        ColStatusCodeCountFieldEnum.status_code_1x_count.value: num_1,
        ColStatusCodeCountFieldEnum.status_code_2x_count.value: num_2,
        ColStatusCodeCountFieldEnum.status_code_3x_count.value: num_3,
        ColStatusCodeCountFieldEnum.status_code_4x_count.value: num_4,
        ColStatusCodeCountFieldEnum.status_code_5x_count.value: num_5
    })


def get_has_payload(text):
    if text == "" or text is None:
        return ValuePayloadVisibleEnum.WITHOUT_PAYLOAD.value
    else:
        return ValuePayloadVisibleEnum.HAS_PAYLOAD.value


def get_visible(has_payload, req_body_parse_err):
    if has_payload:
        if req_body_parse_err:
            return ValuePayloadVisibleEnum.PAYLOAD_VISIBLE.value
        else:
            return ValuePayloadVisibleEnum.PAYLOAD_INVISIBLE.value
    else:
        return ValuePayloadVisibleEnum.PAYLOAD_EMPTY.value


def fill_single_empty(session):
    session.update(text_to_frequency_vector(''))


def fill_single_packet(session, req_body_text, res_body_text, req_body_bytes, res_body_bytes, req_body_parse_err,
                       res_body_parse_err, req_time, res_time, req_header, res_header):
    """
    拆分包后对每个包的填充
    :param session:
    :param req_body_text: str
    :param res_body_text: str
    :param req_body_bytes:
    :param res_body_bytes:
    :param req_body_parse_err:
    :param res_body_parse_err:
    :param req_time:
    :param res_time:
    :param req_header:
    :param res_header:
    :return:
    """
    uris = http_req_path_pattern.findall(req_header)
    req_transfer_encoding = ValueOtherFieldEnum.none.value
    res_transfer_encoding = ValueOtherFieldEnum.none.value
    res_content_encoding = ValueOtherFieldEnum.none.value
    req_content_encoding = ValueOtherFieldEnum.none.value
    for line in req_header.split('\r\n'):
        line_format = line.lower().replace('-', '_')
        if 'content_encoding' in line_format:
            req_content_encoding = line_format.split(': ')[1]
        if 'transfer_encoding' in line_format:
            req_transfer_encoding = line_format.split(': ')[1]
    for line in res_header.split('\r\n'):
        line_format = line.lower().replace('-', '_')
        if 'content_encoding' in line_format:
            res_content_encoding = line_format.split(': ')[1]
        if 'transfer_encoding' in line_format:
            res_transfer_encoding = line_format.split(': ')[1]

    status_code = res_status_code_pattern.findall(res_header)
    if len(status_code) > 0:
        status_code = status_code[0]
    else:
        status_code = 0
    method = http_req_method_pattern.findall(req_header)
    if len(method) > 0:
        method = method[0]
    else:
        method = ''
    req_len = len(req_body_bytes)
    res_len = len(res_body_bytes)
    session.update({
        ColTimeFieldEnum.per_packet_req_time.value: req_time,
        ColTimeFieldEnum.per_packet_res_time.value: res_time,
        ColEntropyFieldEnum.per_packet_req_body.value: entropy(req_body_bytes),
        ColEntropyFieldEnum.per_packet_res_body.value: entropy(res_body_bytes),
        ColEntropyFieldEnum.per_packet_all_body.value: entropy(req_body_bytes + res_body_bytes),
        ColEntropyFieldEnum.per_packet_uri.value: 0 if len(uris) == 0 else entropy(uris[0]),
        ColOtherFieldEnum.req_has_payload.value: get_has_payload(req_body_text),
        ColOtherFieldEnum.res_has_payload.value: get_has_payload(res_body_text),
        ColOtherFieldEnum.req_payload_visible.value: get_visible(get_has_payload(req_body_text), req_body_parse_err),
        ColOtherFieldEnum.res_payload_visible.value: get_visible(get_has_payload(res_body_text), res_body_parse_err),
        ColOtherFieldEnum.req_transfer_encoding.value: req_transfer_encoding,
        ColOtherFieldEnum.res_transfer_encoding.value: res_transfer_encoding,
        ColOtherFieldEnum.req_content_encoding.value: req_content_encoding,
        ColOtherFieldEnum.res_content_encoding.value: res_content_encoding,
        ColOtherFieldEnum.req_uri.value: '' if len(uris) == 0 else uris[0],
        ColOtherFieldEnum.packet_no_res.value: res_body_bytes == b'' and res_header == 'blank',
        ColOtherFieldEnum.res_status_code.value: status_code,
        ColOtherFieldEnum.req_method.value: method,
        ColOtherFieldEnum.req_res_packet_len_rate.value: res_len / req_len if req_len != 0 else 0,
        ColOtherFieldEnum.res_req_packet_len_rate.value: req_len / res_len if res_len != 0 else 0,
    })
    # 是否攻击只看请求
    session.update(text_to_frequency_vector(req_header + req_body_text))


def fill_session_after_single(copied_sessions,
                              session_req_body,
                              session_res_body,
                              session_all_body,
                              req_time_diff_mean=0.0,
                              res_time_diff_mean=0.0, time_diff_mean=0.0):
    """
    填充没拆分的包、拆分后的多个包

    :param copied_sessions:
    :param session_req_body:
    :param session_res_body:
    :param session_all_body:
    :param req_time_diff_mean:
    :param res_time_diff_mean:
    :param time_diff_mean: 每个包的平均时间差、每个包持续时间的均值、即：包内响应-请求组成的列表取其均值
    """
    for session in copied_sessions:
        session.update({
            ColEntropyFieldEnum.session_req_body.value: entropy(session_req_body),
            ColEntropyFieldEnum.session_res_body.value: entropy(session_res_body),
            ColEntropyFieldEnum.session_all_body.value: entropy(session_all_body),
            ColTimeFieldEnum.session_per_packet_req_time_mean.value: req_time_diff_mean,
            ColTimeFieldEnum.session_per_packet_res_time_mean.value: res_time_diff_mean,
            ColTimeFieldEnum.session_per_packet_time_mean.value: time_diff_mean,
        })


def fill_statistic_field(sessions_list, config: ConfigManager, is_update_cache=True, is_abnormal: bool = False):
    """
    计算统计字段
    :param sessions_list:
    :param is_update_cache: 默认将新增的数据同步到本地缓存
    :return:
    """
    for e in list(CacheValueEnum):
        aCount, aMean, aStd = CacheValueUtil.get_cache_value(config.get_config(ConfigColumnEnum.data_cache.value), e,
                                                             is_abnormal)
        if aCount == 0 or aMean == 0 or aStd == 0:
            printx(f'{e.name}本地缓存值为0请先更新缓存值')
            break
        values = []
        for session in sessions_list:
            values.append(session[e.value])
        bMean = np.mean(values)
        bCount = sum(values)
        newCount = aCount + bCount
        newMean = (aCount * aMean + bMean * bCount) / newCount
        aVariance = aStd ** 2
        if bCount == 0:
            bVariance = 0
        else:
            bVariance = sum((x - bMean) ** 2 for x in values) / bCount
        newVariance = (aCount * (aVariance + (aMean - newMean) ** 2) + bCount * (
                bVariance + (bMean - newMean) ** 2)) / newCount
        newStd = math.sqrt(newVariance)
        if is_update_cache:
            data_cache = CacheValueUtil.update_cache_value(e, config.get_config(ConfigColumnEnum.data_cache.value),
                                                           (newCount, newMean, newStd), is_abnormal)
            config.set_config(ConfigColumnEnum.data_cache.value, data_cache)
        for session in sessions_list:
            try:
                # session_per_packet_time_mean
                session.update({
                    f'{e.value}_minus_mean': session[e.value] - newMean,
                    f'{e.value}_z_score': np.nan_to_num((session[e.value] - newMean) / newStd, nan=0),
                })
            except Exception:
                traceback.print_exc()
                printx(e.value)
                session.update({
                    f'{e.value}_minus_mean': 0,
                    f'{e.value}_z_score': 0,
                })
    CacheValueUtil.save_data_cache(config.get_config(ConfigColumnEnum.data_cache.value),
                                   config.get_config(ConfigColumnEnum.data_path.value))
